/*
 * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.baomidou.mybatisplus.advance.injector;

import com.baomidou.mybatisplus.annotation.TableId;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.List;
import lombok.Data;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * 语法树构造器
 *
 * @author wanglei
 * @since 2022-03-14
 */
@Data
public class ASTBuilder {

    private JavacElements elementUtils;

    private TreeMaker treeMaker;

    private Names names;

    private JCTree.JCClassDecl jcClassDecl;

    private Map<String, List<JCTree.JCVariableDecl>> hasMethod = new HashMap<>();

    private final ArrayList<JCTree.JCVariableDecl> fields = new ArrayList<>();

    private final Set<String> fieldNames = new HashSet<>();

    public ASTBuilder(TreeMaker treeMaker, JavacElements elementUtils, Names names, JCTree.JCClassDecl jcClassDecl) {
        this.treeMaker = treeMaker;
        this.elementUtils = elementUtils;
        this.names = names;
        this.jcClassDecl = jcClassDecl;
        //初始化已经存在的方法
        jcClassDecl.defs.stream()
                //开始过滤: 只对变量进行处理
                .filter(it -> it.getKind().equals(Tree.Kind.METHOD))
                //强制类型转换
                .map(it -> (JCTree.JCMethodDecl) it).forEach(it -> {
                    hasMethod.put(it.name.toString(), it.params);
                });
        jcClassDecl.defs.stream()
                //开始过滤: 只对变量进行处理
                .filter(it -> it.getKind().equals(Tree.Kind.VARIABLE))
                //强制类型转换
                .map(it -> (JCTree.JCVariableDecl) it)
                //处理: 生成Getter方法和Setter方法
                .forEach(it -> {
                    if (isHandle(it, jcClassDecl)) {
                        fields.add(it);
                        fieldNames.add(it.name.toString());
                    }
                });
    }

    /**
     * 判断是否包含某个方法
     *
     * @param methodName 方法名
     * @return
     */
    public boolean hasMethod(String methodName) {
        return hasMethod.containsKey(methodName);
    }

    /**
     * 判断是否包含某个属性
     *
     * @param fieldName 属性名
     * @return
     */
    public boolean hasField(String fieldName) {
        return fieldNames.contains(fieldName);
    }

    /**
     * 判断是否需要处理此字段
     *
     * @param jcVariableDecl 字段
     * @param jcClassDecl    类
     * @return
     */
    public boolean isHandle(JCTree.JCVariableDecl jcVariableDecl, JCTree.JCClassDecl jcClassDecl) {
        if (jcVariableDecl.vartype == null || jcVariableDecl.vartype.type == null) {
            return false;
        }
        String typeName = jcVariableDecl.vartype.type.tsym.toString();
        //不是Java开头的不处理
        if (!typeName.startsWith("java.")) {
            return false;
        }
        // todo 排除掉tableField(exits=false)的字段 by wanglei
        try {
            Class clazz = Class.forName(typeName);
            return Comparable.class.isAssignableFrom(clazz);
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    /**
     * 类包含某个方法并且方法参数个数与传入参数个数相同
     *
     * @param annotationClass 注解
     * @param method          属性方法
     * @param value           值
     * @return
     */
    public boolean hasAnnotation(Class<? extends Annotation> annotationClass, String method, Object value) {
        if (this.jcClassDecl.mods == null || this.jcClassDecl.mods.annotations == null) {
            return false;
        }
        // 遍历所有注解 遍历所有属性
        for (JCTree.JCAnnotation annotation : this.jcClassDecl.mods.annotations) {
            if (annotation.type.toString().equals(annotationClass.getName())) {
                for (Pair<Symbol.MethodSymbol, Attribute> methodSymbolAttributePair : annotation.attribute.values) {
                    if (methodSymbolAttributePair.fst.toString().contains(method + "()")) {
                        return methodSymbolAttributePair.snd.toString().equals(value.toString());
                    }
                }
            }
        }
        return false;
    }

    /**
     * 获取类引用
     *
     * @param typeClassName 类的全名
     * @return 类的JCExpression引用
     */
    public JCTree.JCExpression typeRef(String typeClassName) {
        String[] componentArray = typeClassName.split("\\.");
        JCTree.JCExpression expr = treeMaker.Ident(elementUtils.getName(componentArray[0]));
        for (int i = 1; i < componentArray.length; i++) {
            expr = treeMaker.Select(expr, elementUtils.getName(componentArray[i]));
        }
        return expr;
    }

    /**
     * 获取ID的类型
     *
     * @return
     */
    public JCTree.JCExpression getIdTypeRef() {
        //解析id注解
        for (JCTree.JCVariableDecl field : this.fields) {
            if (field.mods != null && field.mods.annotations != null) {
                for (JCTree.JCAnnotation annotation : field.mods.annotations) {
                    if (annotation.type.toString().equals(TableId.class.getName())) {
                        return field.vartype;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 生成一个字段，并且在json中忽略此字段，不进行序列化
     *
     * @param fieldName     字段名
     * @param typeClassName 字段类型
     * @param value         字段值
     * @return
     */
    public JCTree.JCVariableDecl genField(String fieldName, String typeClassName, JCTree.JCExpression value) {
        JCTree.JCVariableDecl result = treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE),
                names.fromString(fieldName), this.typeRef(typeClassName), value);
        //添加MP忽略字段注解
        result.mods.annotations = result.mods.annotations.append(treeMaker.Annotation(typeRef(
                "com.baomidou.mybatisplus.annotation.TableField"), List.of(treeMaker.Assign(treeMaker.Ident(names.fromString("exist")), treeMaker.Literal(false)))));

        //fastjson不序列化
        result.mods.annotations = result.mods.annotations.append(treeMaker.Annotation(typeRef(
                "com.alibaba.fastjson.annotation.JSONField"), List.of(treeMaker.Assign(treeMaker.Ident(names.fromString("serialize")), treeMaker.Literal(false)))));

        //jackson不序列化
        result.mods.annotations = result.mods.annotations.append(treeMaker.Annotation(typeRef(
                "com.fasterxml.jackson.annotation.JsonIgnore"), List.nil()));
        return result;
    }

    /**
     * 调用一个方法
     *
     * @param method     方法名
     * @param types      类型
     * @param paramsList 参数列表
     * @return
     */
    public JCTree.JCMethodInvocation callMethod(String method, String[] types, List<JCTree.JCExpression> paramsList) {
        List<JCTree.JCExpression> paramsTypeList = List.nil();
        for (String type : types) {
            paramsTypeList = paramsTypeList.append(typeRef(type));
        }
        return treeMaker.Apply(paramsTypeList, typeRef(method), paramsList);
    }

    /**
     * 初始化一个对象
     *
     * @param classAllName
     * @return
     */
    public JCTree.JCNewClass newObject(String classAllName, List<JCTree.JCExpression> constructorParams) {
        return treeMaker.NewClass(null,
                com.sun.tools.javac.util.List.nil(),
                typeRef(classAllName),
                constructorParams,
                null);
    }

    /**
     * 构造方法 方法最多只有2行代码，第一行是执行某方法传一些参数，第二个是return xx
     *
     * @param execFunparamsList    调用其他方法
     * @param genMethodParamsList
     * @param execMethodParamTypes
     * @param returnMethodType
     * @param returnStatement
     * @param execMethodAllName
     * @return
     */
    public JCTree.JCMethodDecl genMethod(List<JCTree.JCExpression> execFunparamsList
            , List<JCTree.JCVariableDecl> genMethodParamsList, String[] execMethodParamTypes,
                                          JCTree.JCExpression returnMethodType,
                                          JCTree.JCReturn returnStatement, String execMethodAllName, Name methodName
            ) {
        TreeMaker treeMaker = getTreeMaker();
        //语句列表  先执行语句在返回
        ListBuffer<JCTree.JCStatement> statments = new ListBuffer<JCTree.JCStatement>();
        if (execFunparamsList != null) {
            JCTree.JCMethodInvocation callMethod = callMethod(execMethodAllName,
                    execMethodParamTypes, execFunparamsList);
            statments.append(treeMaker.Exec(callMethod));
        }
        statments.append(returnStatement);

        //方法访问修饰符: public
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);


        //生成方法体
        JCTree.JCBlock body = treeMaker.Block(0, statments.toList());
        //泛型参数列表
        List<JCTree.JCTypeParameter> methodGenericParamList = List.nil();

        //异常抛出列表
        List<JCTree.JCExpression> thrownCauseList = List.nil();
        return treeMaker.MethodDef(
                modifiers,
                methodName,
                returnMethodType,
                methodGenericParamList,
                genMethodParamsList,
                thrownCauseList,
                body,
                null
        );
    }

    /**
     * 添加方法
     * @param method 方法体
     */
    public void addMethod(JCTree.JCMethodDecl method) {
        this.jcClassDecl.defs = this.jcClassDecl.defs.append(method);
        this.hasMethod.put(method.name.toString(),null);
    }

    /**
     * 添加字段
     * @param variableDecl 方法体
     */
    public void addField(JCTree.JCVariableDecl variableDecl) {
        this.jcClassDecl.defs = this.jcClassDecl.defs.append(variableDecl);
        fieldNames.add(variableDecl.name.toString());
    }


}
